<?PHP

/**
 * Cura :  management, administration, care, concern, charge.
 * PHP Class for the passing of PHP sessions to a database, 
 * intended to allow for persistant sessions in a load balanced application.
 * 
 * @author Buzz (D.Busby)
 * @copyright Copyright &copy; 2008, Buzz
 * @license http://creativecommons.org/licenses/by-sa/2.0/uk/ CC BY-SA
 */
class Cura {
	/**
	 * @var String Stores the Session max lifetime
	 */
	private $life_time = 0;
	
	/**
	 * Flag for error log debug writing
	 * 0 = none
	 * 1 = write to php error log
	 * 2 = write to stack and echo out on class destruct
	 */
	private $debug = 0;
	/**
	 * Tag for logs
	 */
	private $dtag = 'CURA DEBUG: ';
	
	private $dstack = array();
	
	
	private function _debug($msg) {
		switch($this->debug) {
			case 0:
				//the debug it does nothing ... *pun*
			break;
			case 1:
				error_log($this->dtag.$msg);
			break;
			case 2:
				array_push($this->dstack, date('r').' '.$msg);
			break;
		}
	}
	/**
	 * Cura class construct
	 * @param string db datbase name
	 * @param string user database user
	 * @param string pwd database passsword
	 * @param string host database host address
	 */
	public function __construct($db,$user,$pwd,$host) {
			$this->_debug('In construct');
			//get the sesison lifetime var
			$this->life_time = get_cfg_var("session.gc_maxlifetime");
			//register objects for session handling
			$res = session_set_save_handler(
				array(&$this, 'open'),
				array(&$this, 'close'),
				array(&$this, 'read'),
				array(&$this, 'write'),
				array(&$this, 'destroy'),
				array(&$this, 'gc')
			);
			if($res) {
				$this->_debug('session handler set');
			} else {
				$this->_debug('failed to set session handler');
			}
			//connect to mySQL
			$this->_con($host,$user,$pwd,$db);
	}
	/**
	 * Cura class destruct
	 */
	public function __destruct() {
		$this->_debug('In destruct');
		if($this->debug == 2){
			echo '<pre>';
			print_r($this->dstack);
			echo '</pre>';
		}
	}
	
	/**
	 * Open session call back
	 */
	public function open() {
		$this->_debug('open()');
		return true;
	}
	/**
	 * Close session call back
	 */
	public function close() {
		$this->_debug('close()');
		return true;
	}
	/**
	 * Reads the session data from the database
	 * @param string sid Session ID
	 * @return string
	 */
	public function read($sid) {
		$this->_debug('read('.$sid.')');
		$return = '';
		//build the SQL
		$sql =	'SELECT session_data FROM sessions WHERE session_id='.$this->_mysql_safe($sid)
			.	' AND ip='.$this->_mysql_safe($_SERVER['REMOTE_ADDR'])
			.	' AND expires > '.time()
			.	' ORDER BY expires DESC;'; //there should never be more than 1 row per session, but just in case there is we want the latest data!
		//get res id
		$res = $this->_query($sql);
		//get data;
		$data = $this->_getdat($res);
		
		$return = $data['session_data'];
		
		return $return;
	}
	/**
	 * Writes the session data to the database
	 * @param string sid session id
	 * @param string data session data
	 * @return bool
	 */
	public function write($sid, $data) {
		/**
		 * ... here as the session data string can get VERY long, and there is no need to log it
		 * even for debug, if you want to know the data print_r($_SESSION)
		 */ 
		$this->_debug('write('.$sid.', ...)');
		//new expiry
		$expire = time() + $this->life_time;
		$sql = 	'REPLACE sessions (session_id,ip,session_data,expires) VALUES ('
			.	$this->_mysql_safe($sid)
			.	', ' . $this->_mysql_safe($_SERVER['REMOTE_ADDR'])
			.	', ' . $this->_mysql_safe($data)
			.	', ' . $this->_mysql_safe($expire)
			.	');';
		if($this->_query($sql)) {
			$this->_debug('Write completed');
			return true;
		} else {
			error_log('Failed to write session');
			error_log('Failed sql: '.$sql);
			error_log('mySQL Error: '.mysql_error());
			return false;
		}
		
	}
	/**
	 * Destroy the session
	 * @param string sid session id
	 * @return bool
	 */
	public function destroy($sid) {
		$this->_debug('destroy('.$sid.')');
		$sql = 	'DELETE FROM sessions WHERE'
			.	' session_id='.$this->_mysql_safe($sid)
			.	' AND ip='.$this->_mysql_safe($_SERVER['REMOTE_ADDR']);
		if($this->_query($sid)) {
			$this->_debug('destroy complete');
			return true;
		} else {
			error_log('Failed to delete session');
			error_log('Failed sql: '.$sql);
			error_log('mySQL Error: '.mysql_error());
			return false;
		}
	}
	/**
	 * Garbage collection
	 * @return bool
	 */
	public function gc(){
		$this->_debug('gc()');
		$sql = 'DELETE FROM sessions WHERE expires < UNIX_TIMESTAMP();';
		if($this->_query($sql)) {
			return true;
		} else {
			error_log('Failed Session Garbage Collection');
			error_log('Failed sql: '.$sql);
			error_log('mySQL Error: '.mysql_error());
			return false;
		}
	} 
	
	/**
	 * mySQL block
	 */
	 
	 /**
	 * @var string Resource ID for mySQL connection
	 */
	 private $conid;
	 
	 private function _mysql_safe($str) {
	 	return '\''.mysql_real_escape_string($str).'\'';
	 }
	 private function _con($host,$user,$pwd,$db) {
	 	$this->conid = mysql_connect($host,$user,$pwd);
	 	if($this->conid) {
	 		mysql_select_db($db,$this->conid);
	 	} else {
	 		error_log('DB Connection failed: '.mysql_error());
	 	}
	 }
	 private function _query($sql) {
	 	return mysql_query($sql);
	 }
	 private function _getdat($res) {
	 	return mysql_fetch_assoc($res);
	 }
	 /**
	  * mySQL block
	  */
}

?>